package Project4;

import Project4.analysis.*;
import Project4.node.*;
import java.util.*;

class AssemblerTAC extends DepthFirstAdapter{
	private String tempName;
	private Statement tempStatement;
	private ExpressionAssignment tempExpression;
	private Assignment tempAssignment;
	private ArrayList<Statement> statementsTAC = new ArrayList<Statement>();

	private ArrayList<FunctionTAC> functions = new ArrayList<FunctionTAC>();
	private FunctionTAC currentFunction;
	private Hashtable<String, Variable> variables = new Hashtable<String, Variable>();

	private int labelCounter = 0;

	public AssemblerTAC(){
		currentFunction = new FunctionTAC("main", true);
		functions.add(currentFunction);
	}

	public void caseAProg(AProg node){
		node.getStmtseq().apply(this);
		currentFunction.addStatements(statementsTAC);
		currentFunction.addVariables(variables);

		for(FunctionTAC f: functions){
			System.out.print(f);
		}
	}

	public void caseAAStmtseq(AAStmtseq node){
		node.getStmt().apply(this);
		node.getStmtseq().apply(this);
	}

	public void caseACStmt(ACStmt node){
		this.tempName = node.getId().getText();
		node.getIdopt().apply(this);
	}

	public void caseADStmt(ADStmt node){
		//backup
		Statement s = tempStatement;

		//if statement (branch on some condition being true)
		tempStatement = new IfStatement(labelCounter++);
		node.getBoolean().apply(this);
		ArrayList<Statement> temp = statementsTAC;
		//if
		statementsTAC = new ArrayList<Statement>();
		node.getStmtseq().apply(this);
		((IfStatement)tempStatement).addIf(statementsTAC);
		//then
		statementsTAC = new ArrayList<Statement>();
		node.getOptionalelse().apply(this);
		((IfStatement)tempStatement).addElse(statementsTAC);

		//restore statementsTAC
		statementsTAC = temp;
		statementsTAC.add(tempStatement);
		tempStatement = s;
	}

	public void caseAEStmt(AEStmt node){
		//backup
		Statement s = tempStatement;

		//while statement
		tempStatement = new WhileStatement(labelCounter++);
		node.getBoolean().apply(this);
		ArrayList<Statement> temp = statementsTAC;
		//condition is true
		statementsTAC = new ArrayList<Statement>();
		node.getStmtseq().apply(this);
		((WhileStatement)tempStatement).addBody(statementsTAC);

		//restore statementsTAC
		statementsTAC = temp;
		statementsTAC.add(tempStatement);
		tempStatement = s;
	}

	public void caseAFStmt(AFStmt node){
		//for loop TODO --still needs to do boolean and interator part
		/*
		ArrayList<Statement> tempStatements = statementsTAC;
		ExpressionAssignment tempE = tempExpression;
		
		tempExpression = new ExpressionAssignment(node.getId().getText(), variables);
		tempStatement = new ForLoopStatement();
		((ForLoopStatement)tempStatement).initialStatement(tempExpression);

		tempStatements = new ArrayList<Statement>();
		node.getStmtseq().apply(this);
		((ForLoopStatement)tempStatement).body(statementsTAC);
		
		//restore
		tempExpression = tempE;
		statementsTAC = tempStatements;*/
	}

	public void caseAGStmt(AGStmt node){
		//for loop TODO (How is this different than the above?)
	}

	public void caseAHStmt(AHStmt node){
		//PUT
		statementsTAC.add(new PutStatement(node.getId().getText(), variables));
	}

	public void caseAIStmt(AIStmt node){
		//TODO return
	}

	public void caseAJStmt(AJStmt node){
		//function declaration
		//backup
		FunctionTAC temp = currentFunction;
		ArrayList<Statement> tempStatementList = statementsTAC;
		Hashtable<String, Variable> tempVariables = variables;

		//function call
		statementsTAC = new ArrayList<Statement>();
		variables = new Hashtable<String, Variable>();
		currentFunction = new FunctionTAC(node.getId().getText()); //name our new friend
		functions.add(currentFunction); //add it to the function list
		node.getVars().apply(this); //get the parameters
		node.getCurlystmtseq().apply(this); //get the methods
		
		currentFunction.addVariables(variables);
		currentFunction.addStatements(statementsTAC);
		
		//restore
		currentFunction = temp;
		statementsTAC = tempStatementList;
		variables = tempVariables;
	}

	public void caseALStmt(ALStmt node){
		//SWITCH statement
		//backup
		Statement t = tempStatement;
		ArrayList<Statement> temp = statementsTAC;
		statementsTAC = new ArrayList<Statement>();

		tempStatement = new SwitchStatement(labelCounter++);
		this.tempExpression = new ExpressionAssignment("$s0", variables);
		node.getExpr().apply(this);
		((SwitchStatement)tempStatement).addExpression(tempExpression);
		

		this.tempExpression = new ExpressionAssignment("$s1", variables);		
		node.getInt().apply(this);
		CaseStatement cs = new CaseStatement(this.tempExpression.getValue());
		node.getLf().apply(this);
		node.getOptionalbreak().apply(this);
		cs.broken = broken;
		cs.addStatements(statementsTAC);
		((SwitchStatement)tempStatement).addStatement(cs);
		node.getCases().apply(this);

		//DEFAULT
		statementsTAC = new ArrayList<Statement>();
		node.getLh().apply(this);
		((SwitchStatement)tempStatement).addDefault(statementsTAC);

		//restore
		statementsTAC = temp;
		statementsTAC.add(tempStatement);
		tempStatement = t;
	}

	public void caseAAIdopt(AAIdopt node){
		//TODO: String assignment?
	}

	public void caseABIdopt(ABIdopt node){
		node.getAssignrule().apply(this);
	}

	private String tempType = "";
	public void caseACIdopt(ACIdopt node){
		String t = tempType;
		node.getType().apply(this);
		variables.put(this.tempName, new Variable(tempName, tempType));
		node.getIdlist().apply(this);
		tempType = t;
	}

	public void caseADIdopt(ADIdopt node){
		this.tempExpression = new ExpressionAssignment(this.tempName, variables);
		this.tempExpression.add(this.tempName);
		this.tempExpression.add("1");
		this.tempExpression.add("+");
		this.statementsTAC.add(tempExpression);
	}

	public void caseAEIdopt(AEIdopt node){
		this.tempExpression = new ExpressionAssignment(this.tempName, variables);
		this.tempExpression.add(this.tempName);
		this.tempExpression.add("1");
		this.tempExpression.add("-");
		this.statementsTAC.add(tempExpression);
	}

	public void caseAFIdopt(AFIdopt node){
		//function call
		Statement temp = tempStatement;

		tempStatement = new FunctionCallStatement(tempName);
		node.getVars2().apply(this); //add vars in that method, I suppose TODO.
		statementsTAC.add(tempStatement);

		//restore
		tempStatement = temp;
	}

	public void caseAAAssignrule(AAAssignrule node){
		this.tempExpression = new ExpressionAssignment(this.tempName, variables);
		node.getExpr().apply(this);
		statementsTAC.add(tempExpression);
	}

	public void caseABAssignrule(ABAssignrule node){
		this.tempAssignment = new Assignment(this.tempName);
		node.getId().getText();
		statementsTAC.add(tempAssignment);
	}

	public void caseAAIdlist(AAIdlist node){
		variables.put(node.getId().getText(), new Variable(node.getId().getText(), tempType));
		node.getIdlist().apply(this);
	}

	public void caseAAOptionalbreak(AAOptionalbreak node){
		broken = true;
	}

	public void caseAEmptyOptionalbreak(AEmptyOptionalbreak node){
		broken = false;
	}

	boolean broken = false;

	public void caseAACases(AACases node){
		//backup
		ArrayList<Statement> temp = statementsTAC;
		statementsTAC = new ArrayList<Statement>();

		this.tempExpression = new ExpressionAssignment("$s1", variables);
		node.getInt().apply(this);
		CaseStatement cs = new CaseStatement(this.tempExpression.getValue());
		node.getStmtseq().apply(this);
		node.getOptionalbreak().apply(this);
		cs.broken = broken;
		cs.addStatements(statementsTAC);
		((SwitchStatement)tempStatement).addStatement(cs);
		node.getCases().apply(this);

		//restore
		statementsTAC = temp;
	}

	public void caseAAVars(AAVars node){
		node.getVarlist().apply(this);
	}

	public void caseAAVarlist(AAVarlist node){
		node.getVarlist().apply(this);
		node.getType().apply(this);
		System.out.println("###### Adding " + node.getId().getText() + " to " + currentFunction.getName());
		variables.put(node.getId().getText(), new Variable(node.getId().getText(), tempType));
	}

	public void caseABVarlist(ABVarlist node){
		node.getType().apply(this);
		System.out.println("###### Adding " + node.getId().getText() + " to " + currentFunction.getName());
		variables.put(node.getId().getText(), new Variable(node.getId().getText(), tempType));
	}

	public void caseAAVars2(AAVars2 node){
		node.getVarlist2().apply(this);
	}

	public void caseAAVarlist2(AAVarlist2 node){
		ExpressionAssignment temp = tempExpression;

		node.getVarlist2().apply(this);
		this.tempExpression = new ExpressionAssignment("$a0", variables);
		node.getExpr().apply(this);
		((FunctionCallStatement)tempStatement).addFunctioncallVar(this.tempExpression);

		tempExpression = temp;
	}

	public void caseABVarlist2(ABVarlist2 node){
		ExpressionAssignment temp = tempExpression;

		this.tempExpression = new ExpressionAssignment("$a0", variables);
		node.getExpr().apply(this);
		((FunctionCallStatement)tempStatement).addFunctioncallVar(this.tempExpression);

		tempExpression = temp;
	}

	public void caseAAExpr(AAExpr node){
		node.getExpr().apply(this);
		node.getTerm().apply(this);
		node.getAddop().apply(this);
	}

	public void caseABExpr(ABExpr node){
		node.getTerm().apply(this);
	}

	public void caseAATerm(AATerm node){
		node.getTerm().apply(this);
		node.getFactor().apply(this);
		node.getMultop().apply(this);
	}

	public void caseABTerm(ABTerm node){
		node.getFactor().apply(this);
	}

	public void caseAAFactor(AAFactor node){
		node.getExpr().apply(this);
	}

	public void caseABFactor(ABFactor node){
		node.getFactor().apply(this);
	}

	public void caseACFactor(ACFactor node){
		node.getInt().apply(this);
	}

	public void caseADFactor(ADFactor node){
		node.getReal().apply(this);
	}

	public void caseAEFactor(AEFactor node){
		//TODO figure out how to use other variables.
	}

	public void caseAFFactor(AFFactor node){
		//function call to get something.
		Statement t = tempStatement;

		tempStatement = new FunctionCallStatement(node.getId().getText());
		node.getVars2().apply(this); //add vars in that method, I suppose TODO.
		statementsTAC.add(tempStatement);

		//restore
		tempStatement = t;
	}

	public void caseAABoolean(AABoolean node){
		//if (true) [AKA: ignore else entirely]
		if(tempStatement instanceof IfStatement){
			((IfStatement)tempStatement).optimizedTrue = true;
		}

		if(tempStatement instanceof WhileStatement){
			((WhileStatement)tempStatement).optimizedTrue = true;
		}
	}

	public void caseABBoolean(ABBoolean node){
		//if (false) [AKA: ignore initial block entirely]
		if(tempStatement instanceof IfStatement){
			((IfStatement)tempStatement).optimizedFalse = true;
		}

		if(tempStatement instanceof WhileStatement){
			((WhileStatement)tempStatement).optimizedFalse = true;
		}
	}

	String conditional = "#ERROR ON CONDITION--";
	public void caseACBoolean(ACBoolean node){
		BooleanStatement bs = new BooleanStatement();
		ExpressionAssignment ea = tempExpression;

		tempExpression = new ExpressionAssignment("$t5", variables);
		node.getCa().apply(this);
		bs.firstExpression(tempExpression);

		tempExpression = new ExpressionAssignment("$t6", variables);
		node.getCb().apply(this);
		bs.secondExpression(tempExpression);

		node.getCond().apply(this);
		//System.out.println("CONDITION: " + conditional);
		bs.condition(conditional);

		if(tempStatement instanceof IfStatement){
			((IfStatement)tempStatement).addBoolean(bs);
		}

		if(tempStatement instanceof WhileStatement){
			((WhileStatement)tempStatement).addBoolean(bs);
		}

		tempExpression = ea;
	}

	public void caseAACond(AACond node){
		conditional = "bne ";
	}

	public void caseABCond(ABCond node){
		conditional = "beq ";
	}

	public void caseACCond(ACCond node){
		conditional = "blt ";
	}

	public void caseADCond(ADCond node){
		conditional = "bgt ";
	}

	public void caseAECond(AECond node){
		conditional = "ble ";
	}

	public void caseAFCond(AFCond node){
		conditional = "bge ";
	}

	public void caseAAAddop(AAAddop node){
		this.tempExpression.add("+");
	}

	public void caseABAddop(ABAddop node){
		this.tempExpression.add("-");
	}

	public void caseAAMultop(AAMultop node){
		this.tempExpression.add("*");
	}

	public void caseABMultop(ABMultop node){
		this.tempExpression.add("/");
	}

	public void caseAAType(AAType node){
		this.tempType = "INT";
	}

	public void caseABType(ABType node){
		this.tempType = "REAL";
	}

	public void caseACType(ACType node){
		this.tempType = "STRING";
	}

	public void caseADType(ADType node){
		this.tempType = "BOOL";
	}

	public void caseAEType(AEType node){
		this.tempType = "VOID";
	}

	public void caseAInt(AInt node){
		String number = node.getNumber().getText();
		this.tempExpression.add(number);
	}
}
